////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef __WN_CONTAINERS_SERIALIZER_H__
#define __WN_CONTAINERS_SERIALIZER_H__

#include "WNCore/inc/WNTypes.h"
#include "WNMemory/inc/WNManipulation.h"
#include "WNMath/inc/WNByteSwap.h"
#include "WNContainers/inc/WNSerializerBase.h"
#include "WNStrings/inc/WNStrings.h"
#include "WNCore/inc/WNAssert.h"

namespace WNContainers {
    template <typename T>
    class WNSerializer : public WNSerializerBase {
    public:
        virtual WN_SIZE_T Serialize(WNDataBuffer& _buffer, WN_UINT32 _serializeFlags) const { 
            WN_RELEASE_ASSERT_DESC(WN_FALSE, "BROKEN");
        }
    };

    template<>
    class WNSerializer<WN_UINT32> : public WNSerializerBase {
        public:
        WNSerializer(WN_UINT32& _item):
            mNumber(_item) {
        }
        virtual WN_SIZE_T Serialize(WNDataBuffer& _buffer, WN_UINT32 _serializeFlags) const {
            switch(_buffer.GetType()) {
                case ReadBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_UINT32), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT32));
                        WNMemory::WNMemCpy(&mNumber, loc, sizeof(WN_UINT32));
                        FromBigEndian(mNumber);
                        return(sizeof(WN_UINT32));
                    }
                case WriteBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_UINT32), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT32));
                        WN_UINT32 num = mNumber;
                        ToBigEndian(num);
                        WNMemory::WNMemCpy(loc, &num, sizeof(WN_UINT32));
                        return(sizeof(WN_UINT32));
                    }
                case ReadText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(16, returnedBytes);
                        WN_SIZE_T outSize = WNStrings::WNReadUInt32(loc, mNumber, returnedBytes - 1);
                        outSize += 1; //consume the space that is supposed to be there
                        return(outSize);
                    }
                case WriteText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(16, returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == 16);
                        WN_SIZE_T outSize = WNStrings::WNWriteUInt32(loc, mNumber, 15);
                        WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");
                        loc[outSize++] = ' ';
                        return(outSize);
                    }
            }
            return(0);
        }
        private:
        WN_UINT32& mNumber;
    };

    template<>
    class WNSerializer<WN_INT32> : public WNSerializerBase {
        public:
        WNSerializer(WN_INT32& _item):
            mNumber(_item) {
        }
        
        virtual WN_SIZE_T Serialize(WNDataBuffer& _buffer, WN_UINT32 _serializeFlags) const {
            switch(_buffer.GetType()) {
                case ReadBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_INT32), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_INT32));
                        WNMemory::WNMemCpy(&mNumber, loc, sizeof(WN_INT32));
                        FromBigEndian(mNumber);
                        return(sizeof(WN_INT32));
                    }
                case WriteBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_INT32), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_INT32));
                        WN_INT32 num = mNumber;
                        ToBigEndian(num);
                        WNMemory::WNMemCpy(loc, &num, sizeof(WN_INT32));
                        return(sizeof(WN_INT32));
                    }
                case ReadText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(16, returnedBytes);
                        WN_SIZE_T outSize = WNStrings::WNReadInt32(loc, mNumber, returnedBytes - 1);
                        outSize += 1; //consume the space that is supposed to be there
                        return(outSize);
                    }
                case WriteText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(17, returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == 17);
                        WN_SIZE_T outSize = WNStrings::WNWriteInt32(loc, mNumber, 16);
                        WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");
                        loc[outSize++] = ' ';
                        return(outSize);
                    }
            }
            return(0);
        }
        private:
        WN_INT32& mNumber;
    };

    template<>
    class WNSerializer<WN_UINT64> : public WNSerializerBase {
        public:
        WNSerializer(WN_UINT64& _item):
            mNumber(_item) {
        }
        
        virtual WN_SIZE_T Serialize(WNDataBuffer& _buffer, WN_UINT32 _serializeFlags) const {
            switch(_buffer.GetType()) {
                case ReadBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_UINT64), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT64));
                        WNMemory::WNMemCpy(&mNumber, loc, sizeof(WN_UINT64));
                        FromBigEndian(mNumber);
                        return(sizeof(WN_UINT64));
                    }
                case WriteBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_UINT64), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT64));
                        WN_UINT64 num = mNumber;
                        ToBigEndian(num);
                        WNMemory::WNMemCpy(loc, &num, sizeof(WN_UINT64));
                        return(sizeof(WN_UINT64));
                    }
                case ReadText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(23, returnedBytes);
                        WN_SIZE_T outSize = WNStrings::WNReadUInt64(loc, mNumber, returnedBytes - 1);
                        outSize += 1; //consume the space that is supposed to be there
                        return(outSize);
                    }
                case WriteText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(23, returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == 23);
                        WN_SIZE_T outSize = WNStrings::WNWriteUInt64(loc, mNumber, 22);
                        WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");
                        loc[outSize++] = ' ';
                        return(outSize);
                    }
            }
            return(0);
        }
        private:
        WN_UINT64& mNumber;
    };

    template<>
    class WNSerializer<WN_INT64> : public WNSerializerBase {
        public:
        WNSerializer(WN_INT64& _item):
            mNumber(_item) {
        }
        
        virtual WN_SIZE_T Serialize(WNDataBuffer& _buffer, WN_UINT32 _serializeFlags) const {
            switch(_buffer.GetType()) {
                case ReadBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_INT64), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_INT64));
                        WNMemory::WNMemCpy(&mNumber, loc, sizeof(WN_INT64));
                        FromBigEndian(mNumber);
                        return(sizeof(WN_INT64));
                    }
                case WriteBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_INT64), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_INT64));
                        WN_INT64 num = mNumber;
                        ToBigEndian(num);
                        WNMemory::WNMemCpy(loc, &num, sizeof(WN_INT64));
                        return(sizeof(WN_INT64));
                    }
                case ReadText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(23, returnedBytes);
                        WN_SIZE_T outSize = WNStrings::WNReadInt64(loc, mNumber, returnedBytes - 1);
                        outSize += 1; //consume the space that is supposed to be there
                        return(outSize);
                    }
                case WriteText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(24, returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == 24);
                        WN_SIZE_T outSize = WNStrings::WNWriteInt64(loc, mNumber, 23);
                        WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");
                        loc[outSize++] = ' ';
                        return(outSize);
                    }
            }
            return(0);
        }
        private:
        WN_INT64& mNumber;
    };

    template<>
    class WNSerializer<WN_UINT8> : public WNSerializerBase {
        public:
        WNSerializer(WN_UINT8& _item):
            mNumber(_item) {
        }
        
        virtual WN_SIZE_T Serialize(WNDataBuffer& _buffer, WN_UINT32 _serializeFlags) const {
            switch(_buffer.GetType()) {
                case ReadBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_UINT8), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT8));
                        WNMemory::WNMemCpy(&mNumber, loc, sizeof(WN_UINT8));
                        return(sizeof(WN_UINT8));
                    }
                case WriteBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_UINT8), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT8));
                        WNMemory::WNMemCpy(loc, &mNumber, sizeof(WN_UINT8));
                        return(sizeof(WN_UINT8));
                    }
                case ReadText: {
                        WN_UINT32 tempNum;
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(4, returnedBytes);
                        WN_SIZE_T outSize = WNStrings::WNReadUInt32(loc, tempNum, returnedBytes - 1);
                        mNumber = tempNum & 0xFF;
                        outSize += 1; //consume the space that is supposed to be there
                        return(outSize);
                    }
                case WriteText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(4, returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == 4);
                        WN_SIZE_T outSize = WNStrings::WNWriteUInt32(loc, mNumber, 3);
                        WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");
                        loc[outSize++] = ' ';
                        return(outSize);
                    }
            }
            return(0);
        }
        private:
        WN_UINT8& mNumber;
    };

    template<>
    class WNSerializer<WN_CHAR*> : public WNSerializerBase {
        public:
        WNSerializer(WN_CHAR*& _item, WN_SIZE_T _length) {
            mString = _item;
            mLength = _length;
        }
        
        virtual WN_SIZE_T Serialize(WNDataBuffer& _buffer, WN_UINT32 _serializeFlags) {
            switch(_buffer.GetType()) {
                case ReadBinary:
                case ReadText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(mLength, returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == mLength);
                        WNMemory::WNMemCpy(mString, loc, mLength);
                        return(mLength);
                    }
                case WriteBinary:
                case WriteText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(mLength, returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == mLength);
                        WNMemory::WNMemCpy(loc, mString, mLength);
                        return(mLength);
                    }
            }
            return(0);
        }

        private:
        WN_SIZE_T mLength;
        WN_CHAR* mString;
    };

    template<>
    class WNSerializer<WN_FLOAT32> : public WNSerializerBase {
    public:
        WNSerializer(WN_FLOAT32& _item):
          mNumber(_item) {
          }

          WN_SIZE_T Serialize(WNDataBuffer& _buffer, WN_UINT32 _serializeFlags) const {
              switch(_buffer.GetType()) {
                    case ReadBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_FLOAT32), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_FLOAT32));
                        WNMemory::WNMemCpy(&mNumber, loc, sizeof(WN_FLOAT32));
                        FromBigEndian(mNumber);
                        return(sizeof(WN_INT32));
                    }
                    case WriteBinary: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(sizeof(WN_FLOAT32), returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_FLOAT32));
                        WN_FLOAT32 num = mNumber;
                        ToBigEndian(num);
                        WNMemory::WNMemCpy(loc, &num, sizeof(WN_FLOAT32));
                        return(sizeof(WN_INT32));
                    }
                    case ReadText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(20, returnedBytes);
                        WN_SIZE_T outSize = WNStrings::WNReadFloat32(loc, mNumber, returnedBytes - 1);
                        outSize += 1; //consume the space that is supposed to be there
                        return(outSize);
                    }
                    case WriteText: {
                        WN_SIZE_T returnedBytes = 0;
                        WN_CHAR* loc = _buffer.ReserveBytes(20, returnedBytes);
                        WN_DEBUG_ASSERT(returnedBytes == 20);
                        WN_SIZE_T outSize = WNStrings::WNWriteFloat32(loc, mNumber, 19);
                        WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");
                        loc[outSize++] = ' ';
                        return(outSize);
                    }
                }
              return(0);
          }
    private:
        WN_FLOAT32& mNumber;
    };
}

#endif // __WN_CONTAINERS_SERIALIZER_H__
